module test_2D

    use, intrinsic :: iso_fortran_env, only: rk => real64
    use testdrive, only: new_unittest, unittest_type, error_type, check
    use queue_m, only: queue_t
    use ntree_factory_m, only: ntree_t, shape_t, make_boundary, make_ntree, make_range, point_t
    use shape_m, only: square_t, point_t, circle_t
    implicit none
    private

    public :: collect_2D

contains

    !> Collect all exported unit tests
    subroutine collect_2D(testsuite)
        !> Collection of tests
        type(unittest_type), allocatable, intent(out) :: testsuite(:)

        testsuite = [ &
                    new_unittest("square_t", test_square_t), &
                    !@todo: add rectangle_t tests
                    ! new_unittest("rectangle_t", test_rectangle_t), &
                    new_unittest("circle_t", test_circle_t), &
                    ! new_unittest("make_boundary", test_make_boundary), &
                    new_unittest("tree_insert", test_tree_insert), &
                    new_unittest("tree_query", test_tree_query) &
                    ]

    end subroutine collect_2D

    subroutine test_square_t(error)
        type(error_type), allocatable, intent(out) :: error
        type(square_t) :: square1, square2
        type(circle_t) :: circle1
        square1 = square_t([0.0_rk, 0.0_rk], 1.0_rk)
        call check(error, square1%center(1), 0.0_rk, "center(1)")
        if (allocated(error)) return
        call check(error, square1%center(2), 0.0_rk, "center(2)")
        if (allocated(error)) return
        call check(error, square1%length, 1.0_rk, "length")
        if (allocated(error)) return

        call check(error, square1%contains(point_t([0.0_rk, 0.0_rk], 1)), .true., "square_t contains")
        if (allocated(error)) return
        call check(error, square1%contains(point_t([1.0_rk, 0.0_rk], 2)), .false., "square_t not contains")
        if (allocated(error)) return
        call check(error, square1%contains(point_t([0.0_rk, 0.5_rk], 3)), .false., "square_t not contains")
        if (allocated(error)) return

        square2 = square_t([0.0_rk, 0.2_rk], 2.0_rk)
        call check(error, square1%intersects(square2), .true., "square_t intersects")
        if (allocated(error)) return
        call check(error, square2%intersects(square1), .true., "square_t intersects")
        if (allocated(error)) return
        square2 = square_t([0.0_rk, -0.4_rk], 1.0_rk)
        call check(error, square1%intersects(square2), .true., "square_t intersects")
        if (allocated(error)) return
        circle1 = circle_t([-0.2_rk, 0.0_rk], 0.5_rk)
        call check(error, circle1%intersects(square1), .true., "square_t intersects")
        if (allocated(error)) return
        call check(error, circle1%intersects(square2), .true., "square_t not intersects")
        call square1%show()

    end subroutine test_square_t

    subroutine test_circle_t(error)
        type(error_type), allocatable, intent(out) :: error
        type(circle_t) :: circle1
        type(square_t) :: square1
        circle1 = circle_t([0.0_rk, 0.0_rk], 1.0_rk)
        call check(error, circle1%center(1), 0.0_rk, "center(1)")
        if (allocated(error)) return
        call check(error, circle1%center(2), 0.0_rk, "center(2)")
        if (allocated(error)) return
        call check(error, circle1%radius, 1.0_rk, "radius")
        if (allocated(error)) return

        call check(error, circle1%contains(point_t([0.3_rk, -0.1_rk], 1)), .true., "circle_t contains")
        if (allocated(error)) return
        call check(error, circle1%contains(point_t([-1.0_rk, 0.0_rk], 2)), .false., "circle_t not contains")
        if (allocated(error)) return

        square1 = square_t([-1.5_rk, 0.0_rk], 1.0_rk)
        call check(error, circle1%intersects(square1), .false., "circle_t not intersects")
        if (allocated(error)) return
        square1 = square_t([1.0_rk, 0.1_rk], 1.0_rk)
        call check(error, circle1%intersects(square1), .true., "circle_t intersects")

        call circle1%show()
    end subroutine test_circle_t

    subroutine test_tree_insert(error)
        type(error_type), allocatable, intent(out) :: error
        type(ntree_t) :: quadtree
        logical info
        call make_ntree(square_t([0.0_rk, 0.0_rk], 1.0_rk), 1, quadtree)
        call quadtree%insert(point_t([0.0_rk, 0.0_rk], 1), info)
        call check(error, info, .true., "insert point")
        if (allocated(error)) return
        call check(error, size(quadtree%points), 1, "size(points)")
        if (allocated(error)) return
        call quadtree%insert(point_t([0.0_rk, -0.7_rk], 2), info)
        call check(error, info, .false., "not insert point")
        if (allocated(error)) return
        call quadtree%insert(point_t([0.0_rk, 0.3_rk], 3), info)
        call check(error, info, .true., "insert point")
        if (allocated(error)) return
        call check(error, size(quadtree%children(2)%points), 1, "size(children(2)%points)")
    end subroutine test_tree_insert

    subroutine test_tree_query(error)
        type(error_type), allocatable, intent(out) :: error
        type(ntree_t) :: quadtree
        type(queue_t) :: queue
        class(*), allocatable :: data
        logical info
        integer i
        integer, parameter :: x(2) = [1, 3]
        call make_ntree(square_t([0.0_rk, 0.0_rk], 1.0_rk), 1, quadtree)
        call quadtree%insert(point_t([0.0_rk, 0.0_rk], 1), info)
        call quadtree%insert(point_t([0.0_rk, -0.7_rk], 2), info)
        call quadtree%insert(point_t([0.0_rk, 0.3_rk], 3), info)
        call quadtree%insert(point_t([0.0_rk, 0.5_rk], 4), info)
        call quadtree%insert(point_t([0.0_rk, 1.0_rk], 5), info)
        call quadtree%insert(point_t([0.0_rk, 1.3_rk], 6), info)
        call quadtree%query(circle_t([0.3_rk, 0.2_rk], 1.0_rk), queue)
        call check(error, queue%size(), 2, "size(queue)")
        if (allocated(error)) return

        do i = 1, queue%size()
            call queue%dequeue(data)
            select type (data)
            type is (integer)
                call check(error, data, x(i), "data")
            end select
        end do

    end subroutine test_tree_query

    ! subroutine test_rectangle_t(error)
    !     type(error_type), allocatable, intent(out) :: error
    !     type(ntree_t) :: bintree
    !     type(line_t) line1
    !     line1 = line_t(0.0_rk, 1.0_rk)
    !     call make_ntree(line1, 2, bintree)
    !     call check(error, bintree%capacity, 2, "tree capacity")
    !     if (allocated(error)) return
    !     call check(error, bintree%divided, .false., "tree not divided")
    !     if (allocated(error)) return
    !     call check(error, allocated(bintree%points), .true., "points allocated")
    !     if (allocated(error)) return
    !     call check(error, allocated(bintree%children), .false., "children not allocated")
    !     if (allocated(error)) return
    !     call bintree%boundary%show()
    ! end subroutine test_rectangle_t

    ! subroutine test_tree_insert(error)
    !     type(error_type), allocatable, intent(out) :: error
    !     type(ntree_t) :: bintree
    !     logical info
    !     call make_ntree(line_t(0.0_rk, 1.0_rk), 1, bintree)
    !     call bintree%insert(point_t([0.5_rk]), info)
    !     call check(error, size(bintree%points), 0, "tree size")
    !     if (allocated(error)) return
    !     call check(error, info, .false., "insert failed")
    !     if (allocated(error)) return

    !     call bintree%insert(point_t([-0.5_rk]), info)
    !     call check(error, size(bintree%points), 0, "tree size")
    !     if (allocated(error)) return
    !     call check(error, info, .false., "insert failed")
    !     if (allocated(error)) return

    !     call bintree%insert(point_t([0.1_rk]), info)
    !     call check(error, size(bintree%points), 1, "tree size")
    !     if (allocated(error)) return
    !     call check(error, info, .true., "insert succeeded")
    !     if (allocated(error)) return
    !     call check(error, bintree%points(1)%x(1), 0.1_rk, "point x")
    !     if (allocated(error)) return

    !     call bintree%insert(point_t([0.23_rk]), info)
    !     call check(error, allocated(bintree%children), .true., "children allocated")
    !     if (allocated(error)) return
    !     call check(error, size(bintree%children), 2, "children size")
    !     if (allocated(error)) return
    !     call check(error, bintree%children(2)%points(1)%x(1), 0.23_rk, "child point x")
    !     if (allocated(error)) return

    !     call bintree%insert(point_t([0.0_rk]), info)
    !     call check(error, bintree%children(1)%points(1)%x(1), 0.0_rk, "child point x")
    !     if (allocated(error)) return
    !     call check(error, bintree%children(2)%divided, .false., "child not divided")

    ! end subroutine test_tree_insert

    ! subroutine test_make_range(error)
    !     type(error_type), allocatable, intent(out) :: error
    !     class(shape_t), allocatable :: range
    !     call make_range([-1.0_rk], 1.0_rk, range)
    !     call range%show()
    !     select type (range)
    !     type is (line_t)
    !         call check(error, range%center(1), -1.0_rk, "range center")
    !         if (allocated(error)) return
    !         call check(error, range%length, 2.0_rk, "range length")
    !     end select
    ! end subroutine test_make_range

    ! subroutine test_tree_query(error)
    !     type(error_type), allocatable, intent(out) :: error
    !     type(ntree_t) :: bintree
    !     type(queue_t) :: queue
    !     class(*), allocatable :: data
    !     class(shape_t), allocatable :: range
    !     logical info
    !     integer i
    !     real(rk), parameter :: x(3) = [0.1_rk, 0.0_rk, 0.23_rk]
    !     call make_ntree(line_t(0.0_rk, 1.0_rk), 1, bintree)
    !     call bintree%insert(point_t([0.1_rk]), info)
    !     call bintree%insert(point_t([0.23_rk]), info)
    !     call bintree%insert(point_t([0.0_rk]), info)
    !     call bintree%insert(point_t([0.5_rk]), info)
    !     call bintree%insert(point_t([-0.35_rk]), info)
    !     call bintree%query(line_t([0.0_rk], 0.5), queue)
    !     call check(error, queue%size(), 3, "queue size")
    !     if (allocated(error)) return
    !     do i = 1, queue%size()
    !         call queue%dequeue(data)
    !         select type (data)
    !         type is (point_t)
    !             call check(error, data%x(1), x(i), "point x")
    !             if (allocated(error)) return
    !         end select
    !     end do
    ! end subroutine test_tree_query

end module test_2D
